package edu.xidian.onlinedocument.websocket;

import com.google.common.collect.Table;
import com.google.common.collect.HashBasedTable;
import edu.xidian.onlinedocument.handler.excpetions.custom.CustomSimpleException;
import edu.xidian.onlinedocument.service.OperationService;
import edu.xidian.onlinedocument.utils.ot.TextManagement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * WebSocket session的管理类，全部TextManagement的管理类，以及记录每个文档的连接数
 * 当发生新连接时读取文档，连接数为0时关闭文档
 */
@Component
public class EditorSocketManager {

    private final Logger logger = LoggerFactory.getLogger(EditorSocketManager.class);

    // 两个key的Map
    // 三元组<R,C,V>，前两个是key，最后一个是value，R表示row，c表示column，这里分别装userId和docId
    private static final Table<Integer, Integer, WebSocketSession> users = HashBasedTable.create();
    // 文档id到TextManagement的映射，当第一次开始编辑文档时创建，当没有人编辑该文档时关闭，每次打开读取数据库，每次修改或关闭保存至数据库
    private static final Map<Integer, TextManagement> textManagement = new ConcurrentHashMap<>();
    // 文档id到文档当前存活连接个数（包括api访问）的映射，每当访问文档或websocket连接时加1，退出减1，如果连接个数为0则关闭该TextManager
    private static final Map<Integer, Integer> aliveConnection = new ConcurrentHashMap<>();

    /**
     * 添加某用户对某文档创建的WebSocket连接进入Map中
     * @param userId 用户id
     * @param docId 文档id
     * @param webSocketSession WebSocket连接
     * @return 是否添加成功
     * @throws Exception 任何异常
     */
    public static boolean add(int userId, int docId, WebSocketSession webSocketSession) throws Exception {
        // 如果已经存在对应的session，则关闭原来的websocket连接并从users中去除
        if(users.contains(userId, docId)) {
            get(userId, docId).close();
            remove(userId, docId);
        }
        users.put(userId, docId, webSocketSession);
        // 如果当前没有该文档的textManagement，则加入
        openTextManagement(docId);
        return true;
    }

    /**
     * 移除某用户对某文档的WebSocket连接
     * @param userId 用户id
     * @param docId 文档id
     * @return 是否移除成功
     */
    public static boolean remove(int userId, int docId) {
        if(!users.contains(userId, docId)) {
            return false;
        }
        users.remove(userId, docId);
        // 如果该用户是最后一个编辑该文档的用户，则关闭textManagement
        if(!users.containsColumn(docId)) {
            closeTextManagement(docId);
        }
        return true;
    }

    public static void removeSameUser(int userId) {
        Set<Integer> docList = users.row(userId).keySet();
        for (Integer docId:
             docList) {
            users.remove(userId, docId);
        }
    }

    public static void removeSameDoc(int docId) {
        Set<Integer> userList = users.column(docId).keySet();
        for (Integer userId:
                userList) {
            users.remove(userId, docId);
        }
    }

    /**
     * 获取某用户对某文档的WebSocket连接
     * @param userId 用户id
     * @param docId 文档id
     * @return 对应的WebSocket连接
     */
    public static WebSocketSession get(int userId, int docId) {
        return users.get(userId, docId);
    }

    /**
     * 获取某用户对所有文档的WebSocket连接
     * @param userId 用户id
     * @return 对应的一组WebSocket连接，(key, value) = (文档id, WebSocket连接)
     */
    public static Map<Integer, WebSocketSession> getSameUser(int userId) {
        return users.row(userId);
    }

    /**
     * 获取某文档对所有用户的WebSocket连接
     * @param docId 文档id
     * @return 对应的一组WebSocket连接，(key, value) = (用户id, WebSocket连接)
     */
    public static Map<Integer, WebSocketSession> getSameDoc(int docId) {
        return users.column(docId);
    }

    public static void printAllSession() {
        System.out.println(users);
    }

    /**
     * 获取一个文档的TextManagement对象
     * @param docId 文档id
     * @return TextManagement
     */
    public static TextManagement getTextManagement(int docId) {
        return textManagement.get(docId);
    }

    /**
     * 打开或创建一个文档的TextManagement对象，并加入到Map中
     * @param docId 文档id
     * @return TextManagement
     */
    private static TextManagement openTextManagement(int docId) {
        TextManagement t = null;
        if(!textManagement.containsKey(docId)) {
            textManagement.put(docId, new TextManagement(docId));
            t = textManagement.get(docId);
            t.read();
        }
        else {
            t = textManagement.get(docId);
        }
        return t;
    }

    public static boolean isTextManagementAlive(int docId) {
        return textManagement.containsKey(docId);
    }

    /**
     * 关闭一个文档的TextManagement对象，并从Map中移除
     * @param docId 文档id
     */
    private static void closeTextManagement(int docId) {
        if(textManagement.containsKey(docId)) {
            textManagement.get(docId).close();
            textManagement.remove(docId);
        }
    }

    /**
     * 当创建WebSocket连接，或者调用文档操作查询接口时，该TextManagement的连接计数器加1
     * 如果Map中不存在该TextManagement，则创建一个新的TextManagement，并设置其连接数为1
     * @param docId 文档id
     */
    public synchronized static TextManagement join(int docId) {
        // 初始化Map对应docId的连接数
        if (!aliveConnection.containsKey(docId)) {
            aliveConnection.put(docId, 0);
        }
        int oldConnections = aliveConnection.get(docId);
        aliveConnection.replace(docId, ++oldConnections);

        if (oldConnections > 1) {
            return getTextManagement(docId);
        }
        // 当该连接是第一个连接时，加载文档
        else if (oldConnections == 1) {
            return openTextManagement(docId);
        }
        return null;
    }

    /**
     * 当断开WebSocket连接，或者文档操作查询接口调用完毕时，该TextManagement的连接计数器减1
     * 当计数器为0时，关闭该TextManagement
     * @param docId 文档id
     */
    public synchronized static void quit(int docId) {
        if (!aliveConnection.containsKey(docId)) {
            throw new CustomSimpleException("quit text management failed: alive connection doesn't contain key " + docId);
        }
        int oldConnections = aliveConnection.get(docId);
        aliveConnection.replace(docId, --oldConnections);
        if (oldConnections == 0) {
            closeTextManagement(docId);
        }
    }
    /*
    public static WebSocketSession get(EditorSocketInfo key) {
        return users.get(key);
    }

    public static WebSocketSession get(int userId, int docId) {
        return users.get(new EditorSocketInfo(userId, docId));
    }
     */
}
